package net.hands.ado.db;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import net.hands.ado.db.annotations.Column;
import net.hands.ado.db.annotations.Index;
import net.hands.ado.db.annotations.Table;
import net.hands.ado.db.annotations.UniqueConstraint;
import net.hands.ado.db.jdbc.dialect.Dialect;
import net.hands.common.AlgorithmUtils;
import net.hands.common.BeanUtils;
import net.hands.common.BeanUtils.PropertyWrapper;
import net.hands.common.I18n;
import net.hands.common.StringUtils;

/**
 * 说明:表 <br>
 * <p>
 * 这是一个开源的软件，请在LGPLv3下合法使用、修改或重新发布。
 * </p>
 * 
 * @author 陈侃(cknet@126.com, 13910090885)
 *         http://code.google.com/p/simpleframework/
 *         http://www.simpleframework.net
 */
public class DBTable implements Serializable {
	private static final long serialVersionUID = -6445073606291514860L;

	private Class<?> beanClass;
	/**
	 * 名称
	 */
	private String name;
	/**
	 * 显示名称
	 */
	private String text;
	/**
	 * 唯一约束容器
	 */
	private Map<String, String[]> uniqueMap = new LinkedHashMap<String, String[]>();

	private DBColumn primaryKey;
	/**
	 * 是否缓存
	 */
	private boolean noCache;

	/**
	 * 数据源地址
	 */
	private String url;

	/**
	 * 字段容器
	 */
	private Map<String, DBColumn> columnMap = new LinkedHashMap<String, DBColumn>();
	/**
	 * 索引容器
	 * 
	 */
	private Map<String, String[]> indexMap = new LinkedHashMap<String, String[]>();

	public Map<String, DBColumn> getColumns() {
		return columnMap;
	}

	public Map<String, String[]> getUniqueMap() {
		return uniqueMap;
	}

	public Map<String, String[]> getIndexMap() {
		return indexMap;
	}

	public void setIndexMap(Map<String, String[]> indexMap) {
		this.indexMap = indexMap;
	}

	public void setUniqueMap(Map<String, String[]> uniqueMap) {
		this.uniqueMap = uniqueMap;
	}

	public void setColumns(Map<String, DBColumn> columns) {
		this.columnMap = columns;
	}

	/**
	 * 
	 * Constructor Method
	 * 
	 * @update 2012-8-6 上午10:10:29
	 * @param name
	 *            表名
	 * @param noCache
	 *            是否支持缓存
	 */
	public DBTable(final Class<?> beanClass) {
		Table tableMeta = beanClass.getAnnotation(Table.class);
		if (tableMeta == null) {
			throw DBException.of("对象没有加上@Table注释");
		}
		this.beanClass = beanClass;
		this.name = StringUtils.hasText(tableMeta.name()) ? tableMeta.name()
				: beanClass.getSimpleName().toLowerCase();
		this.text = tableMeta.text();
		this.url = tableMeta.url();
		UniqueConstraint[] constraintArr = tableMeta.uniqueConstraints();
		Index[] indexArr = tableMeta.indexes();
		for (UniqueConstraint constraint : constraintArr) {// 唯一约束
			uniqueMap.put(
					StringUtils.isBlank(constraint.name()) ? generateKey(this,
							constraint.columnSqlNames()) : constraint.name(),
					constraint.columnSqlNames());
		}
		for (Index index : indexArr) {// 索引
			indexMap.put(
					StringUtils.isBlank(index.name()) ? generateKey(this,
							index.columnSqlNames()) : index.name(),
					index.columnSqlNames());
		}
		if (beanClass.isEnum()) {// 枚举转换
			Field[] fileds = beanClass.getFields();
			Column columnMeta = null;
			for (Field filed : fileds) {
				final DBColumn column = new DBColumn(filed.getName());
				columnMeta = filed.getAnnotation(Column.class);
				convertColumn(columnMeta, column);
				columnMap.put(filed.getName(), column);
			}
		} else {// 对象转换
			for (final PropertyWrapper pw : BeanUtils.getProperties(beanClass)
					.values()) {
				Column meta = null;
				if (pw.field != null) {
					meta = pw.field.getAnnotation(Column.class);
				}
				if (meta == null) {
					meta = pw.getter.getAnnotation(Column.class);
				}
				if (meta != null && meta.ignore()) {
					continue;
				}
				final DBColumn column = new DBColumn(pw.name);
				// col.setPropertyClass(pw.type);
				convertColumn(meta, column);
				if (DBColumn.NO.equals(column.getIsNullable())
						&& !column.isKey()) {
					indexMap.put(generateKey(this, column.getSqlName()),
							new String[] { column.getSqlName() });
				}
				columnMap.put(pw.name, column);
			}
		}

	}

	/**
	 * 
	 * @param name
	 *            表名
	 * @param schema
	 *            表分类 一个用户对应1个schema
	 * @param catalog
	 *            表名的目录名
	 * @param meta
	 * @throws SQLException
	 */
	public DBTable(String name, final DatabaseMetaData meta)
			throws SQLException {
		ResultSet rs = null;
		try {
			this.name = name;
			rs = meta.getColumns(null, null, name, "%");
			DBColumn column = null;
			while (rs.next()) {
				column = new DBColumn(rs);
				columnMap.put(column.getName(), column);
			}
			initIndexes(meta);
		} finally {
			if (rs != null) {
				rs.close();
			}
		}

	}

	/**
	 * 初始索引
	 * 
	 * @param meta
	 * @throws SQLException
	 */
	private void initIndexes(DatabaseMetaData meta) throws SQLException {
		ResultSet rs = null;

		try {
			rs = meta.getIndexInfo(null, null, name, false, true);
			while (rs.next()) {
				if (rs.getShort("TYPE") == DatabaseMetaData.tableIndexStatistic) {// 去除统计索引
					continue;
				}
				String indexName = rs.getString("INDEX_NAME");
				if (indexName == null) {
					return;
				}
				String[] infoArr = indexMap.get(indexName);
				if (infoArr == null) {
					infoArr = StringUtils.split(rs.getString("COLUMN_NAME"),
							",");
				} else {
					infoArr = StringUtils.split(StringUtils.join(infoArr, ",")
							+ "," + rs.getString("COLUMN_NAME"), ",");

				}
				indexMap.put(indexName, infoArr);
			}
		} finally {
			if (rs != null) {
				rs.close();
			}
		}
	}

	/**
	 * 字段注释转移给字段对象
	 * 
	 * @param meta
	 * @param column
	 */
	public void convertColumn(Column meta, final DBColumn column) {
		if (meta != null) {
			column.setType(meta.type());
			column.setLength(meta.length());
			column.setPrecision(meta.precision());
			column.setScale(meta.scale());

			String columnText, columnSqlName, defaultValue;
			if (StringUtils.hasText(columnText = meta.text())) {
				column.setText(I18n.replaceI18n(columnText));
			}
			if (StringUtils.hasText(columnSqlName = meta.name())) {
				column.setSqlName(columnSqlName);
			}
			if (StringUtils.hasText(defaultValue = meta.defaultValue())) {
				column.setDefaultValue(defaultValue);
			}
			column.setIsNullable(meta.isNullable());
			column.setKey(meta.isKey());
			if (column.isKey()) {
				primaryKey = column;
				column.setIsNullable(DBColumn.NO);
			}

		}
	}

	public String getText() {
		return StringUtils.hasText(text) ? text : name;
	}

	/**
	 * 说明:获得表名
	 * 
	 * @return String
	 * @update 2012-8-6 上午10:11:55
	 */
	public String getName() {
		return name;
	}

	public DBColumn getPrimaryKey() {
		return primaryKey;
	}

	public void setPrimaryKey(DBColumn primaryKey) {
		this.primaryKey = primaryKey;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public boolean hasPrimaryKey() {
		return getPrimaryKey() != null;
	}

	public String sqlConstraintString(Dialect dialect) {
		if (hasPrimaryKey()) {
			StringBuilder buf = new StringBuilder("primary key (");
			buf.append(primaryKey.getName());
			return buf.append(')').toString();
		}
		return "";
	}

	/**
	 * 说明:是否支持缓存
	 * 
	 * @return boolean
	 * @update 2012-8-6 上午10:12:43
	 */
	public boolean isNoCache() {
		return noCache;
	}

	/**
	 * 说明:设置缓存
	 * 
	 * @param noCache
	 * @update 2012-8-6 上午10:13:03
	 */
	public DBTable setNoCache(final boolean noCache) {
		this.noCache = noCache;
		return this;
	}

	public Class<?> getBeanClass() {
		return beanClass;
	}

	// @SuppressWarnings("unchecked")
	// public Map<String, DBColumn> getColumns() {
	// final Class<?> beanClass = getBeanClass();
	// return beanClass != null && columnMap != null ? columnMap
	// : Collections.EMPTY_MAP;
	// }

	public String getColSqlName(final String propertyName) {
		final DBColumn column = getColumns().get(propertyName);
		return column != null ? column.getSqlName() : propertyName;
	}

	public String getBeanPropertyName(final String sqlName) {
		for (final DBColumn column : getColumns().values()) {
			if (sqlName.equalsIgnoreCase(column.getSqlName())) {
				return column.getName();
			}
		}
		return sqlName;
	}

	/**
	 * 创建 表sql
	 * 
	 * @param dialect
	 * @return
	 */
	public String sqlCreateTable(Dialect dialect) {
		StringBuilder buf = new StringBuilder();
		if (columnMap != null) {
			buf.append(dialect.getCreateTableString()).append(' ').append(name)
					.append(" (");
			DBColumn column = null;
			int i = 0;
			for (String pwname : columnMap.keySet()) {
				i++;
				column = columnMap.get(pwname);
				buf.append(column.getSqlName()).append(' ');
				buf.append(column.getSqlType(dialect));

				String defaultValue = column.getDefaultValue();
				if (defaultValue != null) {
					buf.append(" default ").append(defaultValue);
				}
				if (DBColumn.YES.equals(column.getIsNullable())) {
					buf.append(dialect.getNullColumnString());
				} else {
					buf.append(" not null");
				}
				if (i != columnMap.size()) {
					buf.append(", ");
				}
			}
			if (hasPrimaryKey()) {
				buf.append(", ").append(sqlConstraintString(dialect));
			}
			String columnComment = column.getText();
			if (columnComment != null) {
				buf.append(dialect.getColumnComment(columnComment));
			}

			buf.append(')');
		}

		return buf.toString();
	}

	/**
	 * 添加字段
	 * 
	 * @param dialect
	 * @return
	 */
	public String sqlAddColumn(Dialect dialect, DBColumn column) {
		StringBuilder buf = new StringBuilder();
		buf.append(dialect.getAddColumnString(name, column.getSqlName()));
		buf.append(column.getSqlType(dialect));

		String defaultValue = column.getDefaultValue();
		if (defaultValue != null) {
			buf.append(" default ").append(defaultValue);
		}
		if (DBColumn.YES.equals(column.getIsNullable())) {
			buf.append(dialect.getNullColumnString());
		} else {
			buf.append(" not null");
		}

		String columnComment = column.getText();
		if (columnComment != null) {
			buf.append(dialect.getColumnComment(columnComment));
		}
		return buf.toString();
	}

	public String sqlDropColumn(Dialect dialect, DBColumn column) {
		return dialect.getDropColumnString(name, column.getSqlName());
	}

	/**
	 * 删除表 sql
	 * 
	 * @param dialect
	 * @param defaultCatalog
	 * @param defaultSchema
	 * @return
	 */
	public String sqlDropTable(Dialect dialect) {
		return dialect.getDropTableString(getName());
	}

	/**
	 * 表唯一约束前缀
	 * 
	 * @return
	 */
	public static String generatedConstraintNamePrefix() {
		return "UK_";
	}

	/***
	 * 生成唯一不重复名称 前缀+表+字段 如约束名或索引名
	 * 
	 * @param prefix
	 * @param table
	 * @param columnMap
	 * @return
	 */
	public static String generateKey(DBTable table, String... columnSqlNames) {
		StringBuilder sb = new StringBuilder("table`" + table.getName() + "`");
		for (String unique : columnSqlNames) {
			String columnName = unique == null ? "" : unique;
			sb.append("column`" + columnName + "`");
		}
		return generatedConstraintNamePrefix()
				+ AlgorithmUtils.md5Hex(sb.toString());
	}

	/*****
	 * 
	 * 添加约束
	 * 
	 * 
	 * 
	 */

	public String[] sqlAddUniques(Dialect dialect) {
		// Do this here, rather than allowing UniqueKey/Constraint to do it.
		// We need full, simplified control over whether or not it happens.
		final int size = uniqueMap.size();
		String[] uniqueSqlArr = new String[size];
		int i = 0;
		String[] uniqueArr = null;
		for (String key : uniqueMap.keySet()) {
			uniqueArr = uniqueMap.get(key);
			uniqueSqlArr[i] = dialect.addUniquesOnAlter(getName(), key,
					uniqueArr);
			i++;
		}

		return uniqueSqlArr;
	}

	/**
	 * 删除约束
	 * 
	 * @param uniqueKey
	 * @return
	 */
	public String[] sqlDropUniques(Dialect dialect) {
		// Do this here, rather than allowing UniqueKey/Constraint to do it.
		// We need full, simplified control over whether or not it happens.
		final int size = uniqueMap.size();
		String[] uniqueSqlArr = new String[size];
		int i = 0;
		for (String key : uniqueMap.keySet()) {
			uniqueSqlArr[i] = dialect.dropUniquesOnAlter(getName(), key);
			i++;
		}

		return uniqueSqlArr;
	}

	/**
	 * 添加索引
	 */
	public String[] sqlAddIndexs(Dialect dialect) {
		final int size = indexMap.size();
		String[] indexSqlArr = new String[size];
		int i = 0;
		String[] indexArr = null;
		for (String key : indexMap.keySet()) {
			indexArr = indexMap.get(key);
			indexSqlArr[i] = dialect.createIndex(getName(), false, key,
					indexArr);
			i++;
		}

		return indexSqlArr;
	}

	/**
	 * 删除索引
	 * 
	 * @param dialect
	 * @return
	 */
	public String[] sqlDropIndex(Dialect dialect) {
		final int size = indexMap.size();
		String[] indexSqlArr = new String[size];
		int i = 0;
		for (String key : indexMap.keySet()) {
			indexSqlArr[i] = dialect.dropIndex(getName(), key);
			i++;
		}

		return indexSqlArr;
	}

	/**
	 * 表字段 加入相应注释
	 */
	public String[] sqlCommentStrings(Dialect dialect) {
		
		List<String> comments = new ArrayList<String>();
		if (dialect.supportsCommentOn()) {
			if (text != null) {
				StringBuilder buf = new StringBuilder()
						.append("comment on table ").append(name)
						.append(" is '").append(text).append("'");
				comments.add(buf.toString());
			}

			for (String key : columnMap.keySet()) {
				DBColumn column = columnMap.get(key);
				String columnComment = column.getText();
				if (columnComment != null) {
					StringBuilder buf = new StringBuilder()
							.append("comment on column ").append(name)
							.append('.').append(column.getName())
							.append(" is '").append(columnComment).append("'");
					comments.add(buf.toString());
				}
			}
		}
		return comments.toArray(new String[comments.size()]);
	}

	@Override
	public String toString() {
		
		return getText();
	}
}
